package server;

import game.GameInstance;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
 * This class stores all the relevant information about the state of the server
 * at anyone one time, that is it keeps track of the number of ongoing games and
 * players currently playing
 *
 *
 * @author nathanposlusny
 * @author Vincent Tsuei
 * @author Andrew Gratta
 * @version 2012-11-21
 */
public class ServerRegistry {

    private GameServer server;

    // Fields for tracking games
    private long gameID = 0;
    private final ReentrantReadWriteLock gameRegistryLock = new ReentrantReadWriteLock();
    private HashMap<Long, GameInstance> games;
    private ArrayList<Long> availID = new ArrayList<Long>();

    ServerRegistry(GameServer initServer) {
        this.server = initServer;
        this.games = new HashMap<Long, GameInstance>();
    }

    /**
     * This method adds the new GameInstance to the hashmap, assigns a unique
     * game id, and returns the unique game id.
     *
     * @param newGame
     * @return
     */
    public long registerGame(GameInstance newGame) {
        if (newGame.wasRegistered()) {
            throw new RuntimeException("Trying to register a game that was registered before.");
        }
        
        gameRegistryLock.writeLock().lock();
        try {
            //Can keep a record of 2^64 games - cache last used game ids.
            long newID;
            if (!availID.isEmpty()) {
                newID = availID.remove(availID.size() - 1);
            } else {
                newID = (gameID++);
            }
            newGame.setRegistered(newID);
            games.put(newID, newGame);
            return newID;
        } finally {
            gameRegistryLock.writeLock().unlock();
        }
    }

    /**
     * Given a game instance, deregister the game. Must be currently registered.
     */
    public GameInstance deregisterGame(GameInstance removeGame) {
        if (removeGame.wasRegistered()) {
            return deregisterGame(removeGame.getGameID());
        } else {
            throw new RuntimeException("Trying to deregister a game that is not currently registered.");
        }
    }

    /**
     * Given a game ID, register the game with that ID. Must be a currently registered game.
     */
    public GameInstance deregisterGame(Long removeGameID) {
        gameRegistryLock.writeLock().lock();
        try {
            if (games.containsKey(removeGameID)) {
                GameInstance ret = games.remove(removeGameID);
                ret.setDeregistered();
                availID.add(removeGameID);
                return ret;
            } else {
                throw new RuntimeException("Trying to deregister a non-registered game instance.");
            }
        } finally {
            gameRegistryLock.writeLock().unlock();
        }
    }
    
    //TODO: forceDeregister() client.forceDeregister() //Just in case
    
    /**
     * Return all registered games as an array. Thread-safe.
     */
    private static final GameInstance[] getAllGamesArr = new GameInstance[0];
    public GameInstance[] getAllGames() {
        gameRegistryLock.readLock().lock();
        try {
            return games.values().toArray(getAllGamesArr);
        } finally {
            gameRegistryLock.readLock().unlock();
        }
    }
    
    /**
     * Get the game with the specified ID.
     * Returns null if the game is not in the registry.
     */
    public GameInstance getGameByID(long gameID)
    {
        return games.get(gameID);
    }
}
